package in.srain.cube.concurrent;

import android.os.SystemClock;
import android.util.Log;

import java.util.Map;

import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ThreadPoolExecutor;

import in.srain.cube.util.Env;
import in.srain.cube.util.UTUtil;
import in.srain.cube.util.log.LogClient;
import in.srain.cube.util.log.LogClientManagerProxy;
import in.srain.cube.util.log.LogEvent;

import static in.srain.cube.util.log.LogClientManagerProxy.CLIENT_NAME_APP_PERFORMANCE;

public class ThreadPoolStatistics {

    private static final int COUNT_THRESHOLD = 5;
    private static final long DURATION_THRESHOLD = 5000;

    private static final RunnableHook RUNNABLE_HOOK = new StatisticsRunnableHook();
    private static final Map<String, RunnableWrapper> RUNNABLE_MAP = new ConcurrentHashMap<>();

    static {
        new WatchThread().start();
    }

    public static void onTaskAdd(ThreadPoolExecutor threadPoolExecutor, String threadPoolName) {
        final long taskCount = threadPoolExecutor.getTaskCount();
        final long completedTaskCount = threadPoolExecutor.getCompletedTaskCount();
        final int activeCount = threadPoolExecutor.getActiveCount();
        final int queueSize = threadPoolExecutor.getQueue().size();
        final int notCompletedCount = activeCount + queueSize; // approximate
        if (notCompletedCount > COUNT_THRESHOLD || Env.isDevOrInDevMode()) {
            addEventLogForReceiveMessage(threadPoolName, taskCount, completedTaskCount, activeCount, queueSize, notCompletedCount);
        }
    }

    private static void addEventLogForReceiveMessage(String threadPoolName, long taskCount, long completedTaskCount, int activeCount, int queueSize, int notCompletedCount) {
        final LogEvent logEvent = new LogEvent("thread");
        logEvent.putData("thread_pool_name", threadPoolName);
        logEvent.putData("task_count", taskCount);
        logEvent.putData("completed_task_count", completedTaskCount);
        logEvent.putData("active_count", activeCount);
        logEvent.putData("queue_size", queueSize);
        logEvent.putData("not_completed_count", notCompletedCount);
        LogClient client = LogClientManagerProxy.Companion.getClientByName(CLIENT_NAME_APP_PERFORMANCE);
        if (client != null) {
            client.addLog(logEvent);
        }
    }

    private static void addEventLogForRunnable(RunnableWrapper runnableWrapper) {
        final LogEvent logEvent = new LogEvent("thread-timeout");
        final Thread runnableThread = runnableWrapper.getThread();
        if (runnableThread != null) {
            logEvent.putData("thread_name", runnableThread.getName());
            logEvent.putData("thread_id", runnableThread.getId());
        }
        logEvent.putData("id", runnableWrapper.getId());
        logEvent.putData("exec_duration", runnableWrapper.getDuration());
        logEvent.putData("exec_from_now", runnableWrapper.getStartFromNow());
        logEvent.putData("detail", Log.getStackTraceString(runnableWrapper.getStarterStackTrace()));
        LogClient client = LogClientManagerProxy.Companion.getClientByName(CLIENT_NAME_APP_PERFORMANCE);
        if (client != null) {
            client.addLog(logEvent);
        }
    }

    private static class StatisticsRunnableHook implements RunnableHook {

        @Override
        public void beforeExecute(Thread t, RunnableWrapper r) {
            RUNNABLE_MAP.put(r.getId(), r);
        }

        @Override
        public void afterExecute(RunnableWrapper r, Throwable t) {
            RUNNABLE_MAP.remove(r.getId());
            if (r.getDurationOrStartFromNow() > DURATION_THRESHOLD) {
                addEventLogForRunnable(r);
            }
        }
    }

    public static RunnableWrapper createRunnableWrapper(Runnable runnable) {
        return new RunnableWrapper(RUNNABLE_HOOK, runnable);
    }

    private static class WatchThread extends Thread {
        public WatchThread() {
            super.setName(WatchThread.class.getSimpleName());
        }

        private static final long CHECK_INTERVAL = 5000;

        @Override
        public void run() {
            while (true) {
                synchronized (this) {
                    long timeout = CHECK_INTERVAL;
                    final long start = SystemClock.elapsedRealtime();
                    while (timeout > 0) {
                        try {
                            wait(timeout);
                        } catch (InterruptedException ignored) {
                        }
                        timeout = CHECK_INTERVAL - (SystemClock.elapsedRealtime() - start);
                    }
                }

                for (RunnableWrapper r : RUNNABLE_MAP.values()) {
                    if (r.getDurationOrStartFromNow() > DURATION_THRESHOLD) {
                        addEventLogForRunnable(r);
                    }
                }
            }
        }
    }
}
